<?php
/**
 * author: NanQi
 * datetime: 2020/10/17 22:09
 */

namespace NanQi\Hope\Ext;


use Hyperf\CircuitBreaker\Annotation\CircuitBreaker as Annotation;
use Hyperf\CircuitBreaker\CircuitBreaker;
use Hyperf\CircuitBreaker\Exception\TimeoutException;
use Hyperf\CircuitBreaker\Handler\AbstractHandler;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Grpc\Parser;
use Hyperf\Grpc\StatusCode;
use Hyperf\GrpcClient\BaseClient;
use Hyperf\GrpcClient\Exception\GrpcClientException;
use Hyperf\GrpcClient\Request;
use InvalidArgumentException;
use NanQi\Hope\Annotation\Grpc;
use NanQi\Hope\Helper;

class GrpcHandler extends AbstractHandler
{
    use Helper;
    const DEFAULT_TIMEOUT = 5;

    protected function process(ProceedingJoinPoint $proceedingJoinPoint, CircuitBreaker $breaker, Annotation $annotation)
    {
        $timeout = $annotation->value['timeout'] ?? self::DEFAULT_TIMEOUT;
        $time = microtime(true);

        /** @var Grpc $annotation */
        $svc = $annotation->svc;

        $refMethod = $proceedingJoinPoint->getReflectMethod();
        $method = $refMethod->getName();
        $argument = $proceedingJoinPoint->getArguments();
        if (is_array($argument) && count($argument) == 1) {
            $argument = $argument[0];
        } else {
            throw new InvalidArgumentException("$svc:$method arguments count error");
        }
        $responseClass = $refMethod->getReturnType()->getName();
        $deserialize = [$responseClass, 'decode'];

        $client = new BaseClient($svc);
        $streamId = retry(3, function () use ($argument, $method, $client) {
            $streamId = $client->send(new Request($method, $argument));
            if ($streamId <= 0) {
                $client->_getGrpcClient();
                // The client should not be used after this exception
                throw new GrpcClientException('Failed to send the request to server', StatusCode::INTERNAL);
            }
            return $streamId;
        }, 100);
        list($reply, $status) = Parser::parseResponse($client->recv($streamId), $deserialize);
        if ($status != 0) {
            throw new GrpcClientException("$svc:$method response status is $status");
        }

        $use = microtime(true) - $time;
        if ($use > $timeout) {
            throw new TimeoutException('timeout, use ' . $use . 's', $status);
        }

        $msg = sprintf('%s::%s success, use %ss.', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName, $use);
        $this->logger->debug($msg);

        return $reply;
    }
}
